//
//  Biz.swift
//  JKTool
//
//  Created by 姜奎 on 2023/4/17.
//

import Foundation
import PathKit

fileprivate func modeReflect(string: String?) -> String {
    guard let mode = string?.uppercased() else { return "PROD" }
    
    switch mode {
    case "SIT","DEBUG":return "SIT"
    case "DEV","LBK_ENV_DEV":return "DEV"
    case "PRE","LBK_ENV_PRE":return "PRE"
    case "PROD","RELEASE":return "PROD"
    default:return "PROD"
    }
}

fileprivate func projectModeReflect(string: String) -> String {
    let mode = string.uppercased()
    
    switch mode {
    case "SIT","DEBUG":return "Debug"
    case "DEV","LBK_ENV_DEV":return "LBK_ENV_DEV"
    case "PRE","LBK_ENV_PRE":return "LBK_ENV_PRE"
    case "PROD","RELEASE":return "Release"
    default: return string
    }
}

fileprivate func machModeReflect(string: String) -> String {
    let mode = string.uppercased()
    
    switch mode {
    case "SIT","DEBUG":return "Debug"
    case "DEV","LBK_ENV_DEV":return "Debug"
    case "PRE","LBK_ENV_PRE":return "Release"
    case "PROD","RELEASE":return "Release"
    default: return string
    }
}

fileprivate func workspaceIdReflect(string: String?) -> String {
    guard let mode = string?.uppercased() else { return "prod" }
    switch mode {
    case "SIT","DEBUG":return "default"
    case "DEV","LBK_ENV_DEV":return "dev"
    case "PRE","LBK_ENV_PRE":return "pre"
    case "PROD","RELEASE":return "prod"
    default: return ""
    }
}

extension JKTool {
    
    struct HBBiz: ParsableCommand {
        
        static var configuration = CommandConfiguration(
            commandName: "biz",
            _superCommandName: "jktool",
            abstract: "还呗特殊脚本", subcommands: [MPaaS.self,
                                              BBSec.self,
                                              Fir.self,
                                              Jenkins.self,
                                              Scanner.self])
    }
}

extension JKTool.HBBiz {
    
    struct MPaaS: ParsableCommand {
        
        static var configuration = CommandConfiguration(
            commandName: "mpaas",
            _superCommandName: "biz",
            abstract: "MPaaS工程处理")
        
        @Option(name: .shortAndLong, help: "工程对应的Scheme")
        var scheme: String
        
        @Option(name: .shortAndLong, help: "代码环境[Debug、LBK_ENV_DEV、LBK_ENV_PRE、Release、SIT、PROD]，default：Debug")
        var configuration: String?
        
        @Option(name: .shortAndLong, help: "执行路径")
        var path: String?
        
        mutating func run() {
            let date = Date.init().timeIntervalSince1970
            guard let project = Project.project(directoryPath: path ?? FileManager.default.currentDirectoryPath) else {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            guard project.rootProject == project else {
                
                return po(tip: "请在项目根目录执行脚本", type: .error)
                
            }
            
            if modeReflect(string: configuration) == "PROD" {
                
                func gitLogFileLastTime(path: String, at sourcePath:String) -> Int? {
                    do {
                        let time = try shellOut(to: .gitLogFileLastTime(path: path), at: sourcePath)
                        return Int(time.prefix(while: {$0 != " "}))
                    } catch {
                        let error = error as! ShellOutError
                        po(tip: "获取(PROD & SIT)/meta.config 最后一次提交日期失败！请检查仓库文件(\(path))\n" + error.message + error.output,type: .error)
                    }
                    return nil
                }
                
                guard let prodTimer = gitLogFileLastTime(path: "\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: "PROD"))/meta.config", at: "\(project.buildsPath)/MPaaSPackage"),
                let sitTimer = gitLogFileLastTime(path: "\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: "SIT"))/meta.config", at: "\(project.buildsPath)/MPaaSPackage"),
                prodTimer >= sitTimer
                else { return po(tip: "meta.config 提交日期比对异常：PROD.commit.time < SIT.commit.time，请检查仓库(\(project.directoryPath))",type: .error) }
            }
            
            
            guard let metaString = try? String(contentsOf: URL(fileURLWithPath: "\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))/meta.config")),
                  let meta = metaString.toDictionary(),
                  let workspaceId = meta["workspaceId"] as? String
            else {
                return po(tip: "meta.config['workspaceId']获取失败，请检查仓库文件(\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))/meta.config)", type: .error)
            }
            
            guard workspaceId == workspaceIdReflect(string: configuration)
            else {
                return po(tip: "meta.config['workspaceId']环境检测失败！请检查仓库文件(\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))/meta.config)", type: .error)
            }
            
            do {
                po(tip: "开始更新配置")
                try shellOut(to: .copyFile(from: "\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))/meta.config", to: "\(project.directoryPath)/MPaaS/Targets/\(scheme)/meta.config"))
                try shellOut(to: .copyFile(from: "\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))/yw_1222.jpg", to: "\(project.directoryPath)/MPaaS/Targets/\(scheme)/yw_1222.jpg"))
                po(tip: "配置更新成功")
            } catch {
                let error = error as! ShellOutError
                po(tip: "配置更新失败，请检查仓库文件(\(project.directoryPath))\n" + error.message + error.output,type: .error)
            }
            
            po(tip: "清理workspace")
            _ = try? shellOut(to: .removeFolder(from: "\(project.buildsPath)/MPaaSPackage/workspace"))
            _ = try? shellOut(to: .createFolder(path: "\(project.buildsPath)/MPaaSPackage/workspace"))
            po(tip: "清理workspace成功")
            
            po(tip: "遍历amr列表")
            
            
            guard let fileList = FileManager.default.getFileList(directoryPath: "\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))") else {
                return po(tip: "【\(project.buildsPath)/MPaaSPackage/\(scheme)/\(modeReflect(string: configuration))】为空", type: .error)
            }
            
            var amr_path_list = [String]()
            
            for file in fileList {
                if file.isDirectory {
                    let amr_path = "\(project.buildsPath)/MPaaSPackage/workspace/\(file.name)"
                    amr_path_list.append(amr_path)
                    _ = try? shellOut(to: .copyFolder(from: file.path, to: amr_path))
                }
            }
            po(tip: "遍历amr列表完成")
            
            po(tip: "生成bizInfo和h5_json")
            var H5_JSON = [String: Any]()
            var BIZ_INFO = [String: Any]()
            var offline_h5json_info = [String: Any]()
            var offline_amr_info = [String: Any]()
            var amr_file_path_list = [FileManager.FileModel]()
            for amr_path in amr_path_list {
                guard let fileList = FileManager.default.getFileList(directoryPath: amr_path) else {
                    return po(tip: "【\(amr_path)】为空", type: .warning)
                }
                
                for var file in fileList {
                    if file.suffix == ".json" {
                        
                        guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: file.path)),
                              let info = jsonString.toDictionary() else {
                            return po(tip: "【\(file.path)】为空", type: .warning)
                        }
                        
                        let first = (info["data"] as! [Any]).first as? [String: Any] ?? [String: Any]()
                        if H5_JSON.isEmpty {
                            H5_JSON = info
                        } else {
                            var data = H5_JSON["data"] as? [Any] ?? [Any]()
                            data.append(first)
                            H5_JSON["data"] = data
                        }
                        offline_h5json_info[first["app_id"] as! String] = first["version"]
                    } else if file.suffix == ".amr" {
                        let amr_name = file.name.prefix(file.name.count - ".amr".count)
                        let amr_app_id = amr_name.components(separatedBy: "_").first
                        let amr_version = amr_name.components(separatedBy: "_").last
                        offline_amr_info[amr_app_id!] = amr_version
                        amr_file_path_list.append(file)
                        let zip_path = file.path.replacingOccurrences(of: ".amr", with: ".zip")
                        let unzip_path = file.path.replacingOccurrences(of: ".amr", with: "")
                        _ = try? shellOut(to: .copyFile(from: file.path, to: zip_path))
                        
                        
                        _ = try? Zip.unzipFile(URL(fileURLWithPath: zip_path), destination: URL(fileURLWithPath: unzip_path), overwrite: false, password: nil)
                        
                        guard let fileList = FileManager.default.getFileList(directoryPath: unzip_path) else {
                            return po(tip: "【\(unzip_path)】为空", type: .warning)
                        }
                        
                        for var file in fileList {
                            if file.suffix == ".tar" {
                                _ = try? shellOut(to: .createFolder(path: "\(unzip_path)/tar"))
                                do {
                                    try shellOut(to: ShellOutCommand(string: "tar -xvf \(file.path) -C \(unzip_path)/tar"))
                                } catch {
                                    let error = error as! ShellOutError
                                    po(tip: "配置更新失败\n" + error.message + error.output,type: .error)
                                }
                            }
                        }
                        
                        if amr_app_id != "99999999" || amr_app_id != "88888888" {
                            guard let fileList = FileManager.default.getFileList(directoryPath: "\(unzip_path)/tar") else {
                                return po(tip: "【\(unzip_path)/tar】为空", type: .warning)
                            }
                            var biz_info = [String: Any]()
                            for file in fileList {
                                if file.name == "hpmfile.json" {
                                    guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: file.path)),
                                          let info = jsonString.toDictionary() else {
                                        return po(tip: "【\(file.path)】为空", type: .warning)
                                    }
                                    biz_info["appInfo"] = [info["appid"] as! String: info["version"]]
                                } else if file.name == "moduleInfo.json" {
                                    guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: file.path)),
                                          let info = jsonString.toDictionary() else {
                                        return po(tip: "【\(file.path)】为空", type: .warning)
                                    }
                                    biz_info["moduleInfo"] = info
                                } else if file.name == "pageInfo.json" {
                                    guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: file.path)),
                                          let info = jsonString.toDictionary() else {
                                        return po(tip: "【\(file.path)】为空", type: .warning)
                                    }
                                    biz_info["pageInfo"] = info
                                }
                            }
                            BIZ_INFO[amr_app_id!] = biz_info
                        }
                    }
                }
                
            }
            
            for (app_id, version) in offline_h5json_info {
                let amr_version = offline_amr_info[app_id]
                if amr_version as! String != version as! String {
                    po(tip: "离线包h5_json与amr文件信息不匹配，请检查！！！",type: .error)
                }
            }
            
            if !H5_JSON.isEmpty {
                _ = try? H5_JSON.toString().write(toFile: "\(project.buildsPath)/MPaaSPackage/workspace/h5_json.json", atomically: true, encoding: .utf8)
            }
            
            if !BIZ_INFO.isEmpty {
                _ = try? BIZ_INFO.toString().write(toFile: "\(project.buildsPath)/MPaaSPackage/workspace/bizInfo.json", atomically: true, encoding: .utf8)
            }
            
            po(tip: "移动amr文件")
            for amr_file_path in amr_file_path_list {
                _ = try? shellOut(to: .copyFile(from: amr_file_path.path, to: "\(project.buildsPath)/MPaaSPackage/workspace/\(amr_file_path.name)"))
                _ = try? shellOut(to: .removeFolder(from: URL(fileURLWithPath: amr_file_path.path).deletingLastPathComponent().path))
            }
            
            po(tip: "生成bundle并替换")
            _ = try? shellOut(to: .removeFolder(from: "\(project.buildsPath)/MPaaSPackage/LBKMPaaSPackage.bundle"))
            
            _ = try? shellOut(to: .copyFolder(from: "\(project.buildsPath)/MPaaSPackage/workspace", to: "\(project.buildsPath)/MPaaSPackage/LBKMPaaSPackage.bundle"))
            
            po(tip: "生成bundle并替换完成[\(GlobalConstants.duration(to: date) + " s")]")
        }
    }
    
    
}

extension JKTool.HBBiz {
    
    struct BBSec: ParsableCommand {
        
        static var configuration = CommandConfiguration(
            commandName: "bbsec",
            _superCommandName: "biz",
            abstract: "还呗特殊脚本,梆梆加固")
        
        @Option(name: .shortAndLong, help: "编译环境")
        var configuration: String

        @Option(name: .long, help: "本次加固代码环境版本")
        var cacheVersion: String

        @Option(name: .long, help: "sec版本")
        var secVersion: String

        @Option(name: .long, help: "sec脚本相对梆梆加固文件路径名称")
        var secScriptPathName: String
        
        @Option(name: .long, help: "sec_config.xml路径")
        var secConfigPath: String
        
        @Option(name: .long, help: "sec_license.lic路径")
        var secLicensePath: String
        
        @Option(name: .shortAndLong, help: "执行路径")
        var path: String?
        
        mutating func run() {
            let date = Date.init().timeIntervalSince1970

            guard let project = Project.project(directoryPath: path ?? FileManager.default.currentDirectoryPath) else {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            if !project.vaild() {
                return po(tip: "【\(project.directoryEntrance)】\(path ?? FileManager.default.currentDirectoryPath)目录下不是个正确的Xcode工程", type: .error)
            }

            let secProject = Project(directoryPath: project.directoryPath + "_sec")

            let lastCacheVersionPath = secProject.directoryPath + "/sec_version.txt"

                // 获取上次加固缓存code
            let lastCacheVersion = try? String(contentsOf: URL(fileURLWithPath: lastCacheVersionPath), encoding: .utf8)

            if cacheVersion == lastCacheVersion {
                po(tip: "【\(project.directoryEntrance)】已存在加固缓存，且加固环境及其配置未变更，无需重新加固！",type: .tip)
                return
            }

            let secConfigPath = secConfigPath
            
            if !FileManager.default.fileExists(atPath: secConfigPath) {
                return po(tip: "【\(project.directoryEntrance)】\(secConfigPath)不存在！", type: .error)
            }
            
            let secLicensePath = secLicensePath
            
            if !FileManager.default.fileExists(atPath: secLicensePath) {
                return po(tip: "【\(project.directoryEntrance)】\(secLicensePath)不存在！", type: .error)
            }

            po(tip: "【\(project.directoryEntrance)】开始加固！")

            guard let sec_config = try? String(contentsOf: URL(fileURLWithPath: secConfigPath)) else {
                return po(tip: "【\(project.directoryEntrance)】\(secConfigPath)无法解析！", type: .error)
            }
            
            let config = sec_config
                .replacingOccurrences(of: "${Version}", with: secVersion)
                .replacingOccurrences(of: "${SourceRoot}", with: project.directoryPath)
                .replacingOccurrences(of: "${XcodeProject}", with: "\(project.directoryPath)/\(project.projectType.entrance())")
                .replacingOccurrences(of: "${Target}", with: project.defaultTarget)
                .replacingOccurrences(of: "${Scheme}", with: project.defaultTarget)
                .replacingOccurrences(of: "${configuration}", with: machModeReflect(string: configuration))


            _ = try? shellOut(to: .createFolder(path: project.buildPath))

            let currentSecConfigPath = project.directoryPath + "/build/currentSecConfig.xml"

            _ = try? config.data(using: .utf8)?.write(to: URL(fileURLWithPath: currentSecConfigPath), options: .atomicWrite)

            _ = try? shellOut(to: ShellOutCommand(string: "./\(secScriptPathName)/tools/bclm -i \(secLicensePath)"),at: "~/Documents/SCShield")
            
            do {
                try shellOut(to: ShellOutCommand(string: "./\(secScriptPathName)/build_tools guider_v3 -i \(currentSecConfigPath) --token offlinemode --online 0 --log-level DEBUG"),at: "~/Documents/SCShield")

                try? cacheVersion.write(toFile: lastCacheVersionPath, atomically: true, encoding: .utf8)
            } catch {
                let error = error as! ShellOutError
                let errorMessage = "【\(project.directoryEntrance)】加固失败：\n" + error.message + error.output

                let errorFilePath = secProject.directoryPath + "/sec_log.log"

                _ = try? errorMessage.write(toFile: errorFilePath, atomically: true, encoding: .utf8)
            }

            let secProjectSecConfigPath = Path(secProject.directoryPath + "/currentSecConfig.xml")

            try? secProjectSecConfigPath.delete()

            try? Path(currentSecConfigPath).copy(secProjectSecConfigPath)

            JKTool.Git.Init.main(["--path", secProject.directoryPath])

            JKTool.Git.Commit.main(["--path", secProject.directoryPath, "--message", cacheVersion])

            po(tip: "【\(secProject.directoryEntrance)】保存加固节点完成",type: .tip)

            po(tip: "【\(project.directoryEntrance)】加固完成！[\(GlobalConstants.duration(to: date) + " s")]")
        }
    }
}

extension JKTool.HBBiz {
    
    struct Fir: ParsableCommand {
        
        static var configuration = CommandConfiguration(
            commandName: "fir",
            _superCommandName: "biz",
            abstract: "还呗特殊脚本,fir上传")
        
        @Option(name: .long, help: "设备类型，default：iOS")
        var sdk: String = "iOS"
        
        @Option(name: .shortAndLong, help: "导出环境，default：Release")
        var configuration: String = "Release"
        
        @Option(name: .long, help: "Scheme")
        var scheme: String
        
        @Option(name: .shortAndLong, help: "更新话术,默认：上传时间")
        var message: String?
        
        @Option(name: .shortAndLong, help: "执行路径")
        var path: String?
        
        mutating func run() {
            let date = Date.init().timeIntervalSince1970
            guard let project = Project.project(directoryPath: path ?? FileManager.default.currentDirectoryPath) else {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            if !project.vaild() {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录下不是个正确的Xcode工程", type: .error)
            }
            
            guard project.rootProject == project else {
                return po(tip: "请在项目根目录执行脚本", type: .error)
            }
            
            
            
            let message = message ?? ({
                //创建一个时间对象
                let today = Date()// 获取格林威治时间（GMT）/ 标准时间
                //获取当前时区
                let zone = NSTimeZone.system
                //获取当前时区和GMT的时间间隔
                let interval = zone.secondsFromGMT()
                //获取当前系统时间
                let now = today.addingTimeInterval(TimeInterval(interval))
                let dateformatter = DateFormatter()

                dateformatter.dateFormat = "YYYY-MM-dd HH:mm:ss"// 自定义时间格式

                let time = dateformatter.string(from: now)
                return time
            })()
            
            do {
                let result = try shellOut(to: ShellOutCommand(string: "fir publish \(project.buildPath)/\(configuration)/\(scheme).\(Platform(sdk).fileExtension()) -c \(message)"))
                
                po(tip:"【\(scheme)】fir 上传成功！[\(result)][\(GlobalConstants.duration(to: date) + " s")]")
            } catch {
                let error = error as! ShellOutError
                po(tip: "【\(scheme)】fir上传失败：\n" + error.message + error.output,type: .error)
            }
        }
    }
}

extension JKTool.HBBiz {
    
    struct Jenkins: ParsableCommand {
        
        static var configuration = CommandConfiguration(
            commandName: "jenkins",
            _superCommandName: "biz",
            abstract: "Jenkins中使用的脚本")
        
        @Option(name: .shortAndLong, help: "导出环境，default：Release")
        var configuration: String = "Release"
        
        @Option(name: .shortAndLong, help: "Scheme")
        var scheme: String
        
        @Option(name: .shortAndLong, help: "分支/tag")
        var branch: String
        
        @Option(name: .long, help: "内容格式请参照：JKTool config")
        var configPath: String?
        
        @Option(name: .long, help: "检查本地是否有自定义脚本，若有则执行自定义脚本[{projectPath}/build.sh]，default：true")
        var checkCustomBuildScript: Bool?
        
        @Option(name: .long, help: "是否使用缓存，default：false")
        var cache: Bool?
        
        @Option(name: .shortAndLong, help: "执行路径")
        var path: String?
        
        mutating func run() {
            let date = Date.init().timeIntervalSince1970
            
            func updateOrInit(path: String?) -> Project?{
                let path = (path ?? FileManager.default.currentDirectoryPath).convertRelativePath()
                if FileManager.default.getFileList(directoryPath: path)?.isEmpty != false {
                    po(tip: "目录不存在或者是空的！(\(path))", type: .warning)
                    JKTool.Modules.Init.main(["--url","git@gitlab.caijj.net:client-ios/LBKShell.git","--branch",branch, "--current-branch", "master_bcds","--path",path, "--force",String(true),"--exit-error",String(true)])
                } else {
                    JKTool.Git.Reset.main(["--path",path,"--recursive",String(true),"--exit-error",String(true)])

                    JKTool.Git.Checkout.main(["--path",path,"--branch",branch,"--current-branch", "master_bcds", "--force",String(true),"--exit-error",String(true)])

                    JKTool.Modules.Update.main(["--path",path,"--prune",String(true),"--remote",String(true),"--branch",branch,"--current-branch", "master_bcds", "--force",String(true),"--exit-error",String(true)])
                }

                return Project.project(directoryPath: path)
            }
            
            guard let project = updateOrInit(path: path) else {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            if !project.vaild() {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录下不是个正确的Xcode工程", type: .error)
            }
            
            guard project.rootProject == project else {
                return po(tip: "请在项目根目录执行脚本", type: .error)
            }
            
            let machMode = machModeReflect(string: configuration)
            let projectMode = projectModeReflect(string: configuration)
            
            func checkConfig(project: Project,configPath: String?) -> String {
                if let configPath = configPath {
                    return configPath
                }
                
                let configsPath = "../HBConfigs".convertRelativePath(absolutPath: project.directoryPath)
                let configPath = "\(configsPath)/\(scheme).json"
                if !FileManager.default.fileExists(atPath: configPath) {
                    _ = try? shellOut(to: .removeFolder(from: "\(configsPath)"))
                    _ = try? shellOut(to: .gitClone(url: "git@gitlab.caijj.net:client-ios/HBConfigs.git",to: configsPath, branch: "jenkins"))
                    po(tip: "配置文件下载完成(\(configsPath))")
                } else {
                    JKTool.Git.Reset.main(["--exit-error",String(true),"--path",configsPath])
                    
                    JKTool.Git.Checkout.main(["--path",configsPath,"--branch","jenkins" , "--force",String(true),"--exit-error",String(true)])
                    
                    JKTool.Git.Pull.main(["--exit-error",String(true),"--path",configsPath])
                }
                
                return configPath
            }
            
            let configPath = checkConfig(project: project, configPath: configPath)

            guard let data = try? Data(contentsOf: URL(fileURLWithPath: configPath)) else {
                return po(tip: "请检查配置文件是否存在！(\(configPath))",type: .error)
            }

            guard let configs = try? JSONDecoder().decode(ProjectConfigModel.self, from: data) else {
                return po(tip: "请检查配置文件格式是否正确！(\(configPath))",type: .error)
            }

            let needSec = configs.secConfig?.needSec

            func jenkins(project: Project) {

                JKTool.Build.Clean.main(["--path",project.directoryPath])

                JKTool.Build.main(["--path",project.directoryPath,"--check-custom-build-script",String(checkCustomBuildScript ?? true),"--cache",String(cache ?? false),"--configuration",machMode,"--exit-error",String(true), "--config-path", configPath, "--need-sec", String(needSec ?? false)])

                JKTool.Archive.main(["--path",project.directoryPath,"--config-path",String(configPath),"--export",String(false),"--scheme",scheme, "--configuration", projectMode])
                
                if projectMode == "Release" {
                    JKTool.Export.main(["--path",project.directoryPath,"--config-path",String(configPath),"--scheme",scheme, "--configuration", "Adhoc"])
                }
                
                JKTool.Export.main(["--path",project.directoryPath,"--config-path",String(configPath),"--scheme",scheme, "--configuration", projectMode])

            }
            
            func ipa(project: Project, projectMode: String){

                guard let exportConfig = configs.exportConfigList.first(where: { $0.configuration == projectMode }) else {
                    return po(tip: "【\(scheme)】exportConfigList没有匹配到\(projectMode))",type: .error)
                }

                var savePath: String
                if let path = exportConfig.saveConfig?.path {
                    savePath = path.convertRelativePath(absolutPath: project.directoryPath)
                } else {
                    savePath = project.buildPath
                }

                let ipaPath = "\(savePath)/\(scheme).ipa"
                
                let linkMapPath = "\(savePath)/linkMap.txt"
                
                var baseIpaParseInfoSuffix = ""
                let mode = projectMode.lowercased()
                switch mode {
                case "debug":
                    baseIpaParseInfoSuffix = "sit"
                case "lbk_env_dev":
                    baseIpaParseInfoSuffix = "dev"
                case "lbk_env_pre":
                    baseIpaParseInfoSuffix = "pre"
                case "release":
                    baseIpaParseInfoSuffix = "appstore"
                case "adhoc":
                    baseIpaParseInfoSuffix = "prod"
                default:
                    baseIpaParseInfoSuffix = "prod"
                }
                let baseIpaParseInfoName = "baseIpaParseInfo_\(baseIpaParseInfoSuffix).txt"
                let baseIpaParseInfoPath = "\(FileManager.default.currentDirectoryPath)/\(baseIpaParseInfoName)"
                
                let currentIpaParseInfoPath = "\(savePath)/ipaParseInfo.txt"

                if FileManager.default.fileExists(atPath: ipaPath) {
                    if FileManager.default.fileExists(atPath: linkMapPath) {
                        JKTool.IpaParse.main(["--ipa-path",ipaPath,"--link-map-path",linkMapPath,"--result-path",savePath])
                    } else {
                        po(tip: "【\(scheme)】ipaParse不包含linkMap信息",type: .warning)
                        JKTool.IpaParse.main(["--ipa-path",ipaPath,"--result-path",savePath])
                    }
                } else {
                    po(tip: "【\(scheme)】ipaParse无法完成，请检查(\(ipaPath))是否生成！",type: .warning)
                }
                
                if FileManager.default.fileExists(atPath: baseIpaParseInfoPath),FileManager.default.fileExists(atPath: currentIpaParseInfoPath) {
                    po(tip: "baseIpaParseInfo文件路径\(baseIpaParseInfoPath)")
                    JKTool.IpaCompare.main(["--base-ipa-parse-info-path",baseIpaParseInfoPath,"--current-ipa-parse-info-path",currentIpaParseInfoPath,"--result-path",savePath])
                } else {
                    po(tip: "【\(scheme)】ipaCompare无法完成，请检查(\(baseIpaParseInfoPath))/(\(currentIpaParseInfoPath))是否存在！",type: .warning)
                }
            }

            jenkins(project: project)
            
            if projectMode == "Release" {
                ipa(project: project, projectMode: "Adhoc")
            }
            
            ipa(project: project, projectMode: projectMode)
            
            po(tip:"【\(scheme)】jenkins流程结束[\(GlobalConstants.duration(to: date) + " s")]")
            
        }
    }
}
extension JKTool.HBBiz {
    
    struct Scanner: ParsableCommand {
        
        static var configuration = CommandConfiguration(
            commandName: "scanner",
            _superCommandName: "biz",
            abstract: "静态代码扫描")
        
        @Option(name: .shortAndLong, help: "扫描器（swiftlint、oclint、infer），default：swiftlint")
        var lints: [String] = ["swiftlint"]
        
        @Option(name: .shortAndLong, help: "导出环境，default：Release")
        var configuration: String = "Release"
        
        @Option(name: .long, help: "内容格式请参照：JKTool config")
        var configPath: String?
        
        @Option(name: .shortAndLong, help: "Scheme")
        var scheme: String
        
        @Option(name: .shortAndLong, help: "分支/tag")
        var branch: String
        
        @Option(name: .shortAndLong, help: "执行路径")
        var path: String?
        
        @Option(name: .long, help: "-Dsonar.host.url")
        var sonarHostUrl: String?
        
        @Option(name: .long, help: "-Dsonar.analysis.buildNumber")
        var sonarBuildNumber: String?
        
        @Option(name: .long, help: "-Dsonar.projectVersion")
        var sonarProjectVersion: String?
        
        @Option(name: .long, help: "-Dsonar.login")
        var sonarLogin: String?
        
        @Option(name: .long, help: "-Dsonar.projectKey")
        var sonarProjectKey: String?
        
        @Option(name: .long, help: "-Dsonar.projectName")
        var sonarProjectName: String?
        
        mutating func run() {
            func checkProject(path: String?) -> Project?{
                let path = (path ?? FileManager.default.currentDirectoryPath).convertRelativePath()
                if FileManager.default.getFileList(directoryPath: path)?.isEmpty != false {
                    po(tip: "目录不存在或者是空的！(\(path))", type: .warning)
                    JKTool.Modules.Init.main(["--url","git@gitlab.caijj.net:client-ios/LBKShell.git","--branch",branch,"--path",path, "--force",String(true),"--exit-error",String(true)])
                }
                
                return Project.project(directoryPath: path)
            }
            
            guard let project = checkProject(path: path) else {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录不存在", type: .error)
            }
            
            if !project.vaild() {
                return po(tip: "\(path ?? FileManager.default.currentDirectoryPath)目录下不是个正确的Xcode工程", type: .error)
            }
            
            guard project.rootProject == project else {
                return po(tip: "请在项目根目录执行脚本", type: .error)
            }
            
            let machMode = machModeReflect(string: configuration)
            
            func checkConfig(project: Project,configPath: String?) -> String {
                if let configPath = configPath {
                    return configPath.convertRelativePath(absolutPath: project.directoryPath)
                }
                
                let configsPath = "../HBConfigs".convertRelativePath(absolutPath: project.directoryPath)
                let configPath = "\(configsPath)/\(scheme).json"
                if !FileManager.default.fileExists(atPath: configPath) {
                    _ = try? shellOut(to: .removeFolder(from: "\(configsPath)"))
                    _ = try? shellOut(to: .gitClone(url: "git@gitlab.caijj.net:client-ios/HBConfigs.git",to: configsPath, branch: "jenkins"))
                    po(tip: "配置文件下载完成(\(configsPath))")
                } else {
                    JKTool.Git.Reset.main(["--exit-error",String(true),"--path",configsPath])
                    
                    JKTool.Git.Checkout.main(["--path",configsPath,"--branch","jenkins" , "--force",String(true),"--exit-error",String(true)])
                    
                    JKTool.Git.Pull.main(["--exit-error",String(true),"--path",configsPath])
                }
                
                return configPath
            }
            
            let configPath = checkConfig(project: project, configPath: configPath)
            
            guard let data = try? Data(contentsOf: URL(fileURLWithPath:configPath)) else {
                return po(tip: "请检查配置文件是否存在！(\(configPath))",type: .error)
            }
            
            guard let configs = try? JSONDecoder().decode(ProjectConfigModel.self, from: data) else {
                return po(tip: "请检查配置文件格式是否正确！(\(configPath))",type: .error)
            }
            
            let lintTypes = lints.compactMap { lint in
                if configs.sonarLintConfig?.validLint?.contains(where: {$0 == lint}) == true {
                    return LintType(rawValue: lint)
                }
                return nil
            }


            if lintTypes.isEmpty {
                po(tip:"【\(scheme)】scanner 没有匹配的扫描方式，请检查扫描配置！")
                return
            }

            func scanner(project: Project) {
                for lint in lintTypes {
                    do {
                        try lint.scan(project: project, scheme: scheme, configuration: machMode, sdk: "iOS", isSimulators: false, configPath: configPath)
                    } catch let error as ShellOutError {
                        po(tip: "【\(scheme)】scan error：\n" + error.message + error.output,type: .error)
                    } catch {
                        po(tip: "【\(scheme)】scan error：\n" + error.localizedDescription,type: .error)
                    }
                }

                let scannerwork = Path(project.directoryPath) + ModulesConfigs.sharedInstance.config.build + "scannerwork"
                if scannerwork.exists {
                    try? scannerwork.delete()
                    po(tip:"【\(scheme)】scanner 清理上次扫描结果完成")
                }

                po(tip:"【\(scheme)】scanner 扫描结果开始上传")
                let sonarScanner = ShellOutCommand.sonarScanner(ModulesConfigs.sharedInstance.config.build, lints: lintTypes, hostUrl: sonarHostUrl, buildNumber: sonarBuildNumber, projectVersion: sonarProjectVersion, login: sonarLogin, projectKey: sonarProjectKey, projectName: sonarProjectName, base: configs.sonarLintConfig?.sonarScannerCommand)
                
                do{
                    try shellOut(to:sonarScanner, at: project.directoryPath)
                } catch {
                    let error = error as! ShellOutError
                    po(tip: "【\(scheme)】sonar Scanner error：\n" + error.message + error.output,type: .error)
                }
            }
            
            JKTool.Git.Reset.main(["--path",project.directoryPath,"--recursive",String(true),"--exit-error",String(true)])
            
            JKTool.Git.Checkout.main(["--path",project.directoryPath,"--branch",branch, "--force",String(true),"--exit-error",String(true)])
            
            JKTool.Modules.Update.main(["--path",project.directoryPath,"--prune",String(true),"--remote",String(true),"--branch",branch, "--force",String(true),"--exit-error",String(true)])
            JKTool.Build.main(["--path",project.directoryPath,"--check-custom-build-script",String(false),"--cache",String(true),"--configuration",machMode,"--exit-error",String(true)])
            
            let date = Date.init().timeIntervalSince1970
            
            po(tip:"======【\(scheme)】scanner 开始======")

            scanner(project: project)
            
            po(tip:"======【\(scheme)】scanner流程结束======[\(GlobalConstants.duration(to: date) + " s")]")
            
        }
    }
}
